'use strict';

/**继承数据。实际上就是 Object.assign 方法，会把第2~n个参数的属性赋值给第一个参数*/
const extend = Object.assign;
/**判断这个是否是对象。基于typeof方法 （数组也是对象） */
const isObject = (val) => {
    return val !== null && typeof val === 'object';
};
/**判断值是否改变，这里使用Object.is，是因为value可能是对象
 * @param oldVal 旧值
 * @param newVal 新值
 * @returns 改变了为true，没改变为false
 */
const hasChange = (oldVal, newVal) => !Object.is(oldVal, newVal);
/**判断target上是否有key这个属性。实际上调用的是 Object.prototype.hasOwnProperty.call 方法
         * @param target 目标对象
         * @param key 目标键值
         * @returns 布尔值，有true，没有false
         */
const hasOwn = (target, key) => {
    if (!target)
        return false;
    return Object.prototype.hasOwnProperty.call(target, key);
};
//#region 处理emit的函数等
/**检测烤肉串命名法 */
const camelizeRE = /-(\w)/g;
/** 把烤肉串命名方式转换成驼峰命名方式 */
const camelize = (str) => str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : ""));
/**  首字母大写 */
const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1);
/** 添加 on 前缀，并且首字母大写 */
const toHandlerKey = (str) => str ? `on${capitalize(str)}` : ``;
/**用来匹配 kebab-case 的情况，比如 onTest-event 可以匹配到 T，然后取到 T 在前面加一个 - 就可以，\BT 就可以匹配到 T 前面是字母的位置 */
const hyphenateRE = /\B([A-Z])/g;
/**用来匹配 kebab-case 的情况*/
const hyphenate = (str) => str.replace(hyphenateRE, "-$1").toLowerCase();
//#endregion

/**装有所有reactive的Map，可以索引到某个对象的键Map */
const targetMap = new WeakMap();
/**当前正在使用的Effect，在effect被run的时候会设置。因为当我们get某个响应式对象的属性时，会使用track收集依赖，需要知道是在收集哪个函数依赖，所以用一个全局变量装着 */
let activeEffect;
/**当前是否应该在track函数中收集依赖。注意每次打开后都要关闭。 因为当调用stop后，如果get了响应式对象，仍然会进行收集依赖，这和我们stop方法相违背了 */
let shouldTrack = false;
/**判断是否应该收集依赖 */
function isTracking() {
    return shouldTrack && (activeEffect !== undefined);
}
/**reactive的effect类 */
class ReactiveEffect {
    /** 构造函数
     * @param fn effect主函数
     * @param scheduler 任务调度器。如果传递了这个，第一次effect初始化的时候会执行fn，后续updated的时候只执行scheduler
     */
    constructor(fn, scheduler) {
        /**这个ReactiveEffect身上被绑定过的dep */
        this.deps = [];
        /**一个flag，用于判断当前effect是否活跃  （如果调用了stop，就停止effect了，就不活跃了） 可以优化性能 */
        this.active = true;
        this.fn = fn;
        if (scheduler)
            this.scheduler = scheduler;
    }
    /**执行这个依赖函数 */
    run() {
        if (!this.active) { //如果是被stop了的
            return this.fn(); //因为 effect 函数需要返回fn，且执行返回值，可以得到fn的返回值，所以这里需要把返回值return出去
        }
        else {
            shouldTrack = true; //如果没被stop，就打开这个开关
            activeEffect = this;
            const res = this.fn();
            shouldTrack = false; //用完记得关闭
            return res;
        }
    }
    /**停止响应式 */
    stop() {
        if (this.active) { //优化性能
            cleanupEffect(this);
            this.active = false;
            if (this.onStop)
                this.onStop(); //触发回调函数 
        }
    }
}
/** effect函数
 * @param fn 要执行的依赖函数
 * @param options 配置项
 * @returns 返回runner函数，即这个依赖函数对应的 ReactiveEffect.run 函数
 */
function effect(fn, options) {
    const _effect = new ReactiveEffect(fn, options === null || options === void 0 ? void 0 : options.scheduler);
    extend(_effect, options); // options 上可能有很多值，用这个方法可以直接把option的值挂载到_effect，不用写那么多句代码
    _effect.run();
    /**返回出去的runner函数 */
    const runner = _effect.run.bind(_effect); //因为在run函数中，涉及到this的问题，所以这里返回出去时，需要绑定一下this
    runner.effect = _effect; //在这里给runner挂载一下effect实例，方便我们后面stop
    return runner;
}
/** 依赖收集函数，给 target.key 收集依赖
 * @param target 目标对象
 * @param key 目标键值
 */
function track(target, key) {
    if (!isTracking())
        return;
    //基于target和key，找到对应的Set，往里面添加依赖函数
    /**拿到target对应的Map，装着所有键值Map。*/
    let depsMap = targetMap.get(target);
    if (!depsMap) { //没有的话就初始化，并且放到全局的响应式Map中
        depsMap = new Map();
        targetMap.set(target, depsMap);
    }
    /**拿到装着该键值依赖的容器Set。*/
    let dep = depsMap.get(key);
    if (!dep) { //没有的话就初始化，并且放到对应Map中
        dep = new Set();
        depsMap.set(key, dep);
    }
    trackEffects(dep);
}
/**把当前活跃的依赖activeEffect添加到目标dep中。从track抽离出来是为了让ref复用
 * @param dep 要被添加的dep
 */
function trackEffects(dep) {
    if (dep.has(activeEffect))
        return; //如果已经有了，就不需要添加了
    dep.add(activeEffect); // 收集
    activeEffect.deps.push(dep);
}
/** 触发指定依赖
 * @param target 目标对象
 * @param key 目标键值
 */
function trigger(target, key) {
    //基于target和key，找到对应的Set，把依赖全部执行
    const depsMap = targetMap.get(target);
    if (!depsMap)
        return;
    const dep = depsMap.get(key);
    if (!dep)
        return;
    triggerEffects(dep);
}
/**触发dep中的所有依赖。从trigger抽离出来是为了让ref复用
 * @param dep 目标dep
 */
function triggerEffects(dep) {
    for (const eff of dep) {
        if (eff.scheduler)
            eff.scheduler(); //有 scheduler 就执行 scheduler
        else
            eff.run(); //没有的话就执行普通的run
    }
}
/**清空这个effect在dep中对应的内容
 * @param effect 要清空的effect
 */
function cleanupEffect(effect) {
    effect.deps.forEach(dep => {
        dep.delete(effect);
    });
}

//写在这里是为了只初始化一次，避免每次都要调用一次函数
/**getter函数。 */
const get = creatGetter();
/**setter函数。 */
const set = createSetter();
/**readonly 的 getter */
const readonlyGet = creatGetter(true);
/**shallowReadonly 的 getter */
const shallowReadonlyGet = creatGetter(true, true);
/**创建一个getter 。高阶函数，会返回一个函数，作为Proxy的处理函数
 * @param isReadonly 是否为只读
 * @param shallow 是否是非递归响应式（即不响应式内部属性）
 * @returns getter函数
 */
function creatGetter(isReadonly = false, shallow = false) {
    /** 返回一个getter函数
    * @param target 需要取值的目标对象
    * @param key 需要获取的值的键值
    * @param receiver 如果target对象中指定了getter，receiver则为getter调用时的this值
    * @returns target.key 的值
   */
    return function get(target, key) {
        if (key === "__v_isReactive" /* ReactiveFlags.IS_REACTIVE */)
            return !isReadonly; //isReactive函数的逻辑在这里
        else if (key === "__v_isReadonly" /* ReactiveFlags.IS_READONLY */)
            return isReadonly; //isReadonly函数的逻辑在这里
        const res = Reflect.get(target, key);
        if (shallow)
            return res; //如果是非递归Proxy，那么直接return即可，不用做下面的操作
        if (isObject(res)) { //如果 target.key 也是对象，那么我们就要递归调用reactive方法，不断的去创建Proxy
            return isReadonly ? readonly(res) : reactive(res); //根据isReadonly进行递归调用
        }
        if (!isReadonly)
            track(target, key); //不是readonly才收集依赖
        return res;
    };
}
/**创建一个setter */
function createSetter() {
    /** setter函数
      * @param target 需要修改的目标对象
      * @param key 需要修改的值的键值
      * @param value 修改后的值
      * @param receiver 如果遇到 setter，receiver则为setter调用时的this值。
      * @returns 返回一个 Boolean 值表明是否成功设置属性。
     */
    return function set(target, key, value) {
        /**通过反射进行设置键值，拿到返回结果：返回一个 Boolean 值表明是否成功设置属性。 */
        const res = Reflect.set(target, key, value);
        trigger(target, key);
        return res;
    };
}
/**普通的Proxy处理器 */
const mutableHandlers = { get, set };
/**readonly的Proxy处理器 */
const readonlyHandlers = {
    get: readonlyGet,
    set(target, key) {
        console.warn(`设置键值 "${String(key)}" 失败: 目标对象是只读的.`, target);
        return true;
    }
};
/**shallowReadonly的Proxy处理器 */
const shallowReadonlyHandlers = extend({}, readonlyHandlers, {
    get: shallowReadonlyGet
});

/** reactive函数。可修改、会递归子属性为Proxy
 * @param raw 接收一个初始化对象
 */
function reactive(raw) {
    return createActiveObject(raw, mutableHandlers);
}
/** reactive函数的 readonly 版本，只可读，不可修改，会递归子属性为Proxy
 * @param raw 接收一个初始化对象
 */
function readonly(raw) {
    return createActiveObject(raw, readonlyHandlers);
}
/** reactive函数的 的 readonly版本 的 非递归版本，表层是readonly，内部属性都是普通的对象
 * @param raw 接收一个初始化对象
 */
function shallowReadonly(raw) {
    return createActiveObject(raw, shallowReadonlyHandlers);
}
/** 创建响应式对象
 * @param raw 接收一个初始化对象
 * @param baseHandlers Proxy的处理器
 */
function createActiveObject(raw, baseHandlers) {
    if (!isObject(raw)) {
        console.warn(`target [${raw}] 必须是一个对象`);
        return raw;
    }
    return new Proxy(raw, baseHandlers);
}

/**ref的类 */ //因为ref使用场景大多数是简单数据类型，string number这种，我们要想知道是否get或者set，之前的proxy就做不到了，需要使用一个类。
class RefImpl {
    constructor(value) {
        /**用于判断是否是ref的一个标识 */
        this.__v_isRef = true;
        this._value = convert(value); //如果value是个对象，那么需要把这个对象转为reactive，再放到ref.value中
        this._rawValue = value;
        this.dep = new Set();
    }
    /**该ref的value值 */
    get value() {
        trackRefValue(this);
        return this._value;
    }
    set value(newValue) {
        if (hasChange(newValue, this._rawValue)) { //做对比的时候，需要用原始值去对比，避免value是个reactive时对比出错
            //一定要记得先修改value，再去触发依赖，避免错误
            this._rawValue = newValue;
            this._value = convert(newValue); //set的时候也要转换
            triggerEffects(this.dep);
        }
    }
}
/**把当前的依赖添加到ref的dep中  */
function trackRefValue(ref) {
    if (isTracking()) {
        trackEffects(ref.dep);
    }
}
/**根据是否是对象，返回原数据或者reactive后的数据
 * @param value 原数据
 * @returns 根据是否是对象，返回原数据或者reactive后的数据
 */
function convert(value) {
    return isObject(value) ? reactive(value) : value;
}
/**ref函数
 * @param value 值
 */
function ref(value) {
    return new RefImpl(value);
}
/**是否是ref */
function isRef(ref) {
    return !!(ref === null || ref === void 0 ? void 0 : ref.__v_isRef); //如果有__v_isRef这个标识，说明就是ref。有可能是undefined，所以需要用 !! 转布尔值
}
/**解构ref。 如果是ref，就返回ref.value，如果不是，直接返回原值 */
function unRef(ref) {
    return isRef(ref) ? ref.value : ref;
}
/** 使用proxy代理数据，使得ref不需要.value。
 * - 当对象中某个属性是ref时，调用此函数，会返回一个proxy，去读取该属性时，就不需要 .value 了。
 * - 使用场景：template中，我们使用setup中的ref不需要.value
 * - 示例可以看 src\reactivity\tests\ref.spec.ts 中的 proxyRefs
 * @param objectWithRefs 原始对象
 */
function proxyRefs(objectWithRefs) {
    return new Proxy(objectWithRefs, {
        get(target, key) {
            return unRef(Reflect.get(target, key));
        },
        set(target, key, value) {
            //在set的时候要注意，如果修改后的value是ref类型，那么实际上就是 target.key = value (替换掉这个ref)
            //如果传递过来的value是普通数据类型，那么就是 target.key.value = value （在原ref的基础上修改）
            if (isRef(target[key]) && !isRef(value)) { //如果target[key]是ref类型，且设置的value不是ref类型
                return (target[key].value = value);
            }
            else { //其它情况都是直接修改原值
                return Reflect.set(target, key, value);
            }
        }
    });
}

/**创建虚拟节点
 * @param type 节点类型，element类型就是div等字符串，组件类型就是用户传入的对象
 * @param props 属性，键值对
 * @param children 孩子，可能是字符串或者vnode数组
 * @returns 虚拟节点
 */
function createVNode(type, props, children) {
    const vnode = {
        type,
        component: null,
        props,
        key: props === null || props === void 0 ? void 0 : props.key,
        children,
        shapeFlag: getShapeFlag(type),
        el: null
    };
    // 基于 children 再次设置 shapeFlag，下面使用了位运算，有关位运算的知识可以看 src\shared\shapeFlags.ts
    if (Array.isArray(children)) { //是数组就把数组那位变成1
        vnode.shapeFlag |= 8 /* ShapeFlags.ARRAY_CHILDREN */;
    }
    else if (typeof children === "string") { //是字符串就把字符串那位变成1
        vnode.shapeFlag |= 4 /* ShapeFlags.TEXT_CHILDREN */;
    }
    // 判断是否children是slot   1.自身是个组件、children是obj
    if (vnode.shapeFlag & 2 /* ShapeFlags.STATEFUL_COMPONENT */) { //如果是组件类型
        if (typeof children === 'object') { //如果children还是个对象
            vnode.shapeFlag |= 16 /* ShapeFlags.SLOTS_CHILDREN */; //那么就修改这个类型
        }
    }
    return vnode;
}
/**基于 type 来初始化是什么类型的组件 */
function getShapeFlag(type) {
    return typeof type === "string" ? 1 /* ShapeFlags.ELEMENT */ : 2 /* ShapeFlags.STATEFUL_COMPONENT */;
}
//用 symbol 作为唯一标识，避免写错，也可以避免用户写这个
/**文本类型的节点 */
const Text = Symbol("Text");
/**Fragment类型的节点  特殊标签  - 只渲染children，自身不会出现在DOM中  */
const Fragment = Symbol("Fragment");
// 其中还有几个类型比如： static fragment comment
/**创建text类型的vnode  （没有标签包裹） */
function createTextVNode(text = " ") {
    return createVNode(Text, {}, text);
}

/**h函数，是对 createVNode 的一层封装
 * @param type 节点类型，element类型就是div等字符串，组件类型就是用户传入的对象
 * @param props 属性，键值对
 * @param children 孩子，可能是字符串或者vnode数组
 * @returns 虚拟节点
 */
function h(type, props, children) {
    return createVNode(type, props, children);
}

/**处理slots。
 * - 在这里的时候，slots已经只有两种情况了，字符串或vnode数组（在initSlots函数中已经把单个vnode转为数组了）
 * - 本质和h函数是一样的
 * @param slots 所有插槽。键值对，键是插槽名，值是一个插槽函数，函数的返回值是vnode或vnode数组
 * @param name 插槽名 （具名插槽）
 * @param props 需要传递的参数
 * @returns vnode （如果传递的不是函数，就会返回undefined报错）
 */
function renderSlots(slots, name, props) {
    /**获取到要渲染的slot */
    const slot = slots[name];
    if (slot) {
        if (typeof slot === 'function') {
            //使用幽灵标签 Fragment ，只渲染子节点 （Fragment的处理在patch函数中）
            return createVNode(Fragment, {}, slot(props)); //这时候的插槽已经被 initSlots函数 转换为 ()=>vnode[]  形式了
        }
        else {
            console.warn('你传递的slots不是函数形式！！');
            return;
        }
    }
}

/**emit函数，用于调用自定义事件并传参
 * @param instance 组件实例
 * @param event 事件名
 * @param rawArgs 参数
 */
function emit(instance, event, ...rawArgs) {
    // 1. emit 是基于 props 里面的 onXXX 的函数来进行匹配的
    // 所以我们先从 props 中看看是否有对应的 event handler
    const props = instance.props;
    // ex: event -> click 那么这里取的就是 onClick
    // 让事情变的复杂一点如果是烤肉串命名的话，需要转换成  change-page -> changePage
    // 需要得到事件名称
    let handler = props[toHandlerKey(camelize(event))];
    // 如果上面没有匹配的话 那么在检测一下 event 是不是 kebab-case 烤肉串 类型
    if (!handler) {
        handler = props[(toHandlerKey(hyphenate(event)))];
    }
    if (handler) {
        handler(...rawArgs);
    }
}

/**初始化props。这里没有管attrs，暂时直接把 rawProps 赋值给了 instance，实际上会有更多操作，所以才会封装成函数*/
function initProps(instance, rawProps) {
    // TODO： 应该还有 attrs 的概念，如果组件声明了 props 的话，那么才可以进入 props 属性内，不然的话是需要存储在 attrs 内，这里暂时直接赋值给 instance.props 即可
    instance.props = rawProps || {};
}

/**因为可能会有很多内容会挂载在这个proxy上，比如$el $emit等，所以在这里用一个对象来放函数，简化下面的处理器代码 */
const publicPropertiesMap = {
    /**实现从$el中获取, this.$el.xxxx */
    $el: (i) => i.vnode.el,
    /**实现从$slots中获取插槽的vnode数据 */
    $slots: (i) => i.slots,
    /**实现使用 $props 获取props */
    $props: (i) => i.props
};
/**组件实例的proxy的处理器 */
const PublicInstanceProxyHandlers = {
    get({ _: instance }, key) {
        const { setupState, props } = instance;
        //先实现从setupState中获取，直接可以this.xxx读取setupState的xxx
        if (setupState && key in setupState) {
            return setupState[key];
        }
        //实现props获取，直接可以this.xxx读取setupState的xxx
        if (hasOwn(setupState, key)) {
            return setupState[key];
        }
        else if (hasOwn(props, key)) {
            return props[key];
        }
        /**从 publicPropertiesMap 取出对应key的内容 */
        const publicGetter = publicPropertiesMap[key];
        if (publicGetter) { //判断存不存在，存在就调用
            return publicGetter(instance);
        }
    }
};

/**初始化slots，把插槽从vnode转为vnode数组
 * @param instance 组件实例
 * @param children 该实例的children
 */
function initSlots(instance, children) {
    const { vnode } = instance;
    if ((vnode.shapeFlag & 16 /* ShapeFlags.SLOTS_CHILDREN */) && children) { //如果孩子是slot类型，才干这事  （且 children 不是undefined）
        normalizeObjectSlots(children, instance.slots); //这里是处理slot的函数，children不可能是string
    }
}
/**遍历原插槽rawSlots（即children），处理单个插槽，转为 ()=>vnode[]，放到 slots 中*/
const normalizeObjectSlots = (rawSlots, slots) => {
    if (!rawSlots)
        return;
    for (const key in rawSlots) {
        const value = rawSlots[key];
        if (typeof value === "function") {
            // 把这个函数给到slots 对象上存起来,后续在 renderSlot函数 中调用
            // TODO 这里没有对 value 做 normalize，默认 slots 返回的就是一个 vnode 对象 
            //在这里， value原本是一个函数，执行之后才能调用 normalizeSlotValue 函数来进行转换。然后我最终有需要一个函数，所以变成一个箭头函数的样子，等到执行这个箭头函数，才会执行 normalizeSlotValue
            slots[key] = (props) => normalizeSlotValue(value(props));
        }
    }
};
/**判断这个单个插槽是不是数组，不是的话就转为数组 */
const normalizeSlotValue = (value) => {
    return Array.isArray(value) ? value : [value]; // 把 function 返回的值从 vnode | vnode[] 转换成 vnode[] ，这样 slot 就可以支持多个元素了
};

/**当前组件实例。因为 getCurrentInstance 函数拿不到，所以需要借助全局变量（在执行setup的时候赋值，这样才能保证setup的时候拿到正确的数据） */
let currentInstance = null;
/**创建组件实例并返回
 * @param vnode 该组件的虚拟节点
 * @param parent 父组件实例
 * @returns
 */
function createComponentInstance(vnode, parent) {
    // console.log("createComponentInstance时，父组件是", parent);
    const component = {
        isMounted: false,
        vnode,
        parent,
        provides: parent ? parent.provides : {},
        slots: {},
        emit: () => { },
        setupState: {},
        props: {},
        next: null,
        type: vnode.type,
        subTree: {
            el: null,
            type: '',
            shapeFlag: 1,
            key: 0,
            component: null
        }
    };
    component.emit = emit.bind(null, component); //在这里使用bind手动传入第一个参数，用户在使用的时候就不用传递第一个参数了
    return component;
}
/**给实例加工 ，处理props和slots，执行setup并处理返回值、挂载proxy、挂载render等
 * @param instance 组件实例
 */
function setupComponent(instance) {
    initProps(instance, instance.vnode.props);
    initSlots(instance, instance.vnode.children);
    setupStatefulComponent(instance);
}
/**初始化一个有状态的component。
 * - 在里面创建proxy(实现在render中this.用setup的东西、能使用$el获取组件根容器DOM) 和ctx
 * - 调用setup（没有setup的就调用vue2那部分的）
 * @param instance 组件实例
 */
function setupStatefulComponent(instance) {
    //拿到用户写的setup，执行
    /**组件的类型 */
    const component = instance.type;
    //在这里挂载proxy，让 src\runtime-core\renderer.ts 中的 setupRenderEffect函数 能在调用render的时候call绑定this。
    instance.proxy = new Proxy({ _: instance }, PublicInstanceProxyHandlers); //在这里使用 _ 传入instance，这样在get操作的时候就可以解构出来
    /**拿到组件的setup */
    const { setup } = component; //因为这里是处理component的函数，所以一定是这个类型
    if (setup) { //用户有可能没写setup。写了的就调用setup
        setCurrentInstance(instance); //赋值当前组件实例
        /**setup返回值，可能返回function 或 Object */
        const setupResult = setup(shallowReadonly(instance.props), { emit: instance.emit }); //在这里进行setup的传参。props需要用shallowReadonly包裹，因为是只读属性
        handleSetupResult(instance, setupResult);
        setCurrentInstance(null); //执行完setup后就清空
    }
}
/**处理 setupResult，setup返回值不同，得到的东西也不一样
 * - 如果是函数的话，就是渲染函数，如果是对象的话，里面的东西就拿出来
 * - 在这里进行 proxyRefs ，让render中可以直接使用ref
 * @param instance 组件实例
 * @param setupResult setup的返回值
 */
function handleSetupResult(instance, setupResult) {
    //TODO function
    if (typeof setupResult === 'object') {
        instance.setupState = proxyRefs(setupResult); //挂载结果，并使用 proxyRefs 进行代理 ，让render中可以直接使用ref
    }
    finishComponentSetup(instance);
}
/**完成组件setup，挂载render
 * @param instance 组件实例
 */
function finishComponentSetup(instance) {
    const component = instance.type;
    if (component.render) { //如果传递了render函数，那么就挂载到组件实例身上  
        instance.render = component.render;
    }
}
/**获得当前组件实例  **必须在setup中才能使用** */
function getCurrentInstance() {
    return currentInstance;
}
/**设置当前组件实例，封装成函数，可以方便的知道这个全局变量是什么时候被改变的 */
function setCurrentInstance(instance) {
    currentInstance = instance;
}

// 组件的 provide 和 inject 功能  （父组件通过provide提供的数据，可以让任意子孙组件通过inject获取）  例子在example\api-provide-inject
// 这里获取数据要注意，需要向上找所有祖组件，不能单纯只找父组件 ，同时，如果同时存在多个相同键值，取 离自己组件最近的那个
//（我原本的想法是用循环去查找，经过视频讲解之后知道了更好的方法，就是通过原型链，会自动向上查找） 
/**provide-inject 之提供数据。 **必须在setup中才能使用**
 * @param key 键
 * @param value 值
 */
function provide(key, value) {
    /**获取当前组件实例 */
    const currentInstance = getCurrentInstance(); //因为这俩函数必须在setup中使用，所以我们也可以在这里使用 getCurrentInstance
    if (currentInstance) {
        let { provides, parent } = currentInstance;
        /**父组件的provides */
        const parentProvides = parent === null || parent === void 0 ? void 0 : parent.provides;
        //如果父组件的和自己的是一样的，说明是初始化  （因为在createComponentInstance函数中，执行了 provides: parent ? parent.provides : {} 这句代码 ）
        //第二次调用provide的时候，因为已经在这个if中赋值了，就不会再经过if了 ,避免覆盖了
        if (parentProvides === provides) {
            //把当前的provides的原型设置为父组件的provides
            provides = currentInstance.provides = Object.create(parentProvides); //使用 Object.create 设置原型，这一步只能执行一次，避免覆盖
        }
        provides[key] = value; //存
    }
}
/**provide-inject 之获取数据 **必须在setup中才能使用**
 * @param key  键
 * @param defaultVal  如果没取到provide的数据，就返回这个默认值 （可以是函数）
 */
function inject(key, defaultVal) {
    /**获取当前组件实例 */
    const currentInstance = getCurrentInstance(); //因为这俩函数必须在setup中使用，所以我们也可以在这里使用 getCurrentInstance
    if (currentInstance) {
        const { parent } = currentInstance;
        const parentProvide = parent === null || parent === void 0 ? void 0 : parent.provides;
        if (parentProvide && key in parentProvide) { //如果key在parentProvide中有，就返回
            return parentProvide[key];
        }
        else if (defaultVal) { //没有的话就返回自身的默认值
            if (typeof defaultVal === 'function') {
                return defaultVal(); //是函数的话给出函数返回值
            }
            else {
                return defaultVal;
            }
        }
        else {
            return undefined; //都没命中就给undefined
        }
    }
}

//在这里利用高阶函数，让 createApp 能够用到 render函数  （render函数在 src\runtime-core\renderer.ts 的 createRenderer 函数内部用return的方式导出）
function createAppAPI(render) {
    /**创建APP组件  （用户直接引入的函数是在 src\runtime-dom\index.ts 中定义的，在那里调用了高阶函数导出了DOM的render的createAPP）
     * @param  rootComponent  根组件
     */
    return function createApp(rootComponent) {
        return {
            /**挂载方法
             * @param rootContainer 根容器
             */
            mount(rootContainer) {
                //先转为虚拟节点 vnode，后续所有操作都会基于虚拟节点
                const vnode = createVNode(rootComponent);
                render(vnode, rootContainer);
            }
        };
    };
}

/**判断组件是否需要更新 **只要是 props 发生改变了，那么这个 component 就需要更新**
 * @param prevVNode 旧Vnode
 * @param nextVNode 新Vnode
 * @returns 是否需要更新的布尔值
 */
function shouldUpdateComponent(prevVNode, nextVNode) {
    /**旧props */
    const prevProps = prevVNode.props;
    /**新props */
    const nextProps = nextVNode.props;
    //   const emits = component!.emitsOptions; 
    // props 没有变化，那么不需要更新
    if (prevProps === nextProps)
        return false;
    // 如果之前没有 props，那么就需要看看现在有没有 props 了， 所以这里基于 nextProps 的值来决定是否更新
    if (!prevProps)
        return !!nextProps; //双叹号把变量转为布尔值
    // 之前有值，现在没值，那么肯定需要更新
    if (!nextProps)
        return true;
    // 以上都是比较明显的可以知道 props 是否是变化的， 在 hasPropsChanged 会做更细致的对比检测
    return hasPropsChanged(prevProps, nextProps);
}
/**进行更细致的新旧props对比，判断是否需要更新
 * @param prevProps 旧props
 * @param nextProps 新props
 * @returns 是否需要更新的布尔值
 */
function hasPropsChanged(prevProps, nextProps) {
    // 依次对比每一个 props.key
    /**新props的key数组 */
    const nextKeys = Object.keys(nextProps);
    if (nextKeys.length !== Object.keys(prevProps).length) { // 提前对比一下 length ，length 不一致肯定是需要更新的
        return true;
    }
    // 只要现在的 prop 和之前的 prop 不一样那么就需要更新
    for (let i = 0; i < nextKeys.length; i++) {
        const key = nextKeys[i];
        if (nextProps[key] !== prevProps[key]) {
            return true;
        }
    }
    return false;
}

/**队列 */
const queue = [];
/**判断现在是否是job等待状态 */
let isFlushPending = false;
/**Promise.resolve()，用于微任务 */
const p = Promise.resolve();
/**nextTick函数，在这个函数中的callback或await后，就能拿到更新之后的视图 （微任务时执行）
 * @param fn
 */
function nextTick(callback) {
    return callback ? p.then(callback) : p;
}
/**把JOB添加到微任务队列中，同步代码执行完后将会依次执行微任务
 * @param job
 */
function queueJob(job) {
    if (!queue.includes(job)) { //没有在队列里才添加 
        queue.push(job);
    }
    queueFlush();
}
/**判断是否需要执行所有的 job*/
function queueFlush() {
    // 如果同时触发了两个组件的更新的话，这里就会触发两次 then （微任务逻辑）
    // 但是这是没有必要的，我们只需要触发一次即可处理完所有的 job 调用
    // 所以需要判断一下 如果已经触发过 nextTick 了， 那么后面就不需要再次触发一次 nextTick 逻辑了
    if (isFlushPending)
        return;
    isFlushPending = true;
    nextTick(flushJobs);
}
/**执行所有job */
function flushJobs() {
    isFlushPending = false;
    let job;
    while (job = queue.shift()) {
        job && job();
    }
}

/**可以让用户自定义渲染器  （默认是DOM渲染，可以自己定义写成canvas渲染等）
 * @param options 用户传入的自定义渲染函数
 * @returns  在这里返回createApp函数，让createApp的时候能够用到render
 */
function createRenderer(options) {
    const { createElement, patchProp, insert, remove, setElementText } = options; //对应的默认DOM渲染器的代码，在runtime-dom中
    //#region 在这里给函数改名，这样我们就能知道，如果出错了，是哪个函数出错了。 其实可以在解构的时候改名，但是这样无法添加jsDoc注释，不方便阅读代码
    /**用户自定义 - 创建元素函数 - 默认是DOM创建
     * @param vnodeType 虚拟节点type，比如div等
     * @returns 返回创建的元素，一般是DOM，或者自定义的内容
     */
    const hostCreateElement = createElement;
    /**用户自定义 - 挂载属性函数 - 默认是DOM属性挂载
     * @param el 被创建出来的元素，一般是DOM，或者自定义的内容
     * @param key 属性的键
     * @param preVal 旧值 （可能为null， 说明是初始化）
     * @param nextVal 新值 （可能为null， 说明属性被删除了）
     */
    const hostPatchProp = patchProp;
    /**用户自定义 - 插入元素函数 - 默认是DOM插入
     * @param child 要插入的元素，一般是DOM，或者自定义的内容
     * @param parent 被插入的父元素
     * @param anchor 锚点，用于指定插入到哪个元素前面
     */
    const hostInsert = insert;
    /**用户自定义 - 移除单个孩子 - 默认是DOM移除
     * @param el 要被移除的元素，一般是DOM
     */
    const hostRemove = remove;
    /**用户自定义 - 将元素的孩子设置为text - 默认是设置为DOM的text
     * @param el 要被设置的元素
     * @param text 元素的孩子（字符串类型）
     */
    const hostSetElementText = setElementText;
    //#endregion
    //#region 内部函数
    /**render函数 。只有在createApp的时候才调用
     * @param vnode 虚拟节点
     * @param container 容器
     * @param parentComponent 父组件实例
    */
    function render(vnode, container) {
        //调用patch
        patch(null, vnode, container, undefined, null); //这里需要第四五个参数，但是这个函数是app根组件调用的，所以这俩都是没有的（懒得改那么多函数的ts报错了，直接any这里了）
    }
    /**patch函数，最重要的函数，会对比n1和n2，来判断是需要更新还是创建，操作的是Element类型还是Component类型，然后进行递归创建/更新节点
     * @param n1 旧Vnode  （如果是初始化，这个就是null）
     * @param n2 新Vnode
     * @param container 容器
     * @param parentComponent 父组件实例
     * @param anchor 插入的锚点， 如果是需要插入的话，将要插在这个节点之前
     */
    function patch(n1, n2, container, parentComponent, anchor) {
        const { shapeFlag, type } = n2;
        switch (type) {
            case Fragment:
                processFragment(n1, n2, container, parentComponent, anchor); // 这里用 ！，因为懒得改ts报错
                break;
            case Text:
                processText(n1, n2, container);
                break;
            default: //没有命中的。说明不是特殊标签，进行正常的element或component渲染
                if (shapeFlag & 1 /* ShapeFlags.ELEMENT */) { //位运算判断是否是element
                    processElement(n1, n2, container, parentComponent, anchor); // 这里用 ！，因为懒得改ts报错
                }
                else if (shapeFlag & 2 /* ShapeFlags.STATEFUL_COMPONENT */) { //位运算判断是否是组件
                    processComponent(n1, n2, container, parentComponent, anchor); // 这里用 ！，因为懒得改ts报错
                }
                break;
        }
    }
    /**处理组件类型，分为初始化和更新俩种
     * @param n1 旧虚拟节点 （如果是初始化，这个就是null）
     * @param n2 新虚拟节点
     * @param container 容器
     * @param parentComponent 父组件实例
     * @param anchor 插入的锚点， 如果是需要插入的话，将要插在这个节点之前
     */
    function processComponent(n1, n2, container, parentComponent, anchor) {
        if (!n1) {
            mountComponent(n2, container, parentComponent, anchor);
        }
        else {
            updateComponent(n1, n2);
        }
    }
    /**初始化组件
     * @param initialVNode 初始化的Vnode
     * @param container 容器
     * @param parentComponent 父组件实例
     * @param anchor 插入的锚点， 如果是需要插入的话，将要插在这个节点之前
     */
    function mountComponent(initialVNode, container, parentComponent, anchor) {
        /**获得组件实例 */
        const instance = (initialVNode.component = createComponentInstance(initialVNode, parentComponent)); //顺便给 vnode.component 赋值
        setupComponent(instance);
        setupRenderEffect(instance, initialVNode, container, anchor);
    }
    /**组件的更新
     * @param n1 旧vnode
     * @param n2 新vnode
     * @param container 容器DOM
     */
    function updateComponent(n1, n2, container) {
        var _a;
        console.log("更新组件", n1, n2);
        /**更新后的组件实例*/
        const instance = (n2.component = n1.component); //同时，顺便给新n2的component赋值
        if (shouldUpdateComponent(n1, n2)) { // 先看看这个组件是否应该更新
            instance.next = n2; // 那么 next 就是新的 vnode 了（也就是 n2）   
            (_a = instance.update) === null || _a === void 0 ? void 0 : _a.call(instance); //需要的话就手动调用之前的effect的runner ，调用 update 再次更新调用 patch 逻辑， 在update 中调用的 next 就变成了 n2了
        }
        else { // 不需要更新的话，那么只需要覆盖下面的属性即可 
            n2.component = n1.component;
            n2.el = n1.el;
            instance.vnode = n2;
        }
    }
    /**组件-setupRenderEffect：在里面赋值instance.update、调用render、收集触发依赖、触发生命周期、递归调用patch
     * @param instance 当前组件实例
     * @param initialVNode 初始化的VNode
     * @param container 容器DOM
     * @param anchor 插入的锚点， 如果是需要插入的话，将要插在这个节点之前
     */
    function setupRenderEffect(instance, initialVNode, container, anchor) {
        instance.update = effect(() => {
            if (!instance.isMounted) { //如果还未挂载，就走初始化逻辑 
                const { proxy } = instance;
                /**render函数返回的虚拟节点树 */ //同时给 instance.subTree 赋值，方便更新的时候拿到
                const subTree = (instance.subTree = instance.render.call(proxy)); //在这里绑定this 。 proxy的初始化在 src\runtime-core\component.ts 的 setupStatefulComponent函数
                //调用patch，处理子组件   vnode->patch    vnode -> element -> mountElement
                patch(null, subTree, container, instance, anchor); //这里的父组件就是instance了，因为接下来在递归子组件 
                //在这里给el赋值。因为我们需要保证全部子组件都渲染了，这时候赋值的el才是根元素 （我们在 mountElement 函数中就不断的在给每个vnode赋值它的el，在最后，给组件赋值根节点）
                initialVNode.el = subTree.el;
                instance.isMounted = true;
            }
            else { // 否则走更新逻辑  
                const { proxy, next, vnode } = instance;
                if (next) { //next是下一次的虚拟节点，vnode是当前的虚拟节点
                    next.el = vnode.el; //更新next的el
                    updateComponentPreRender(instance, next);
                }
                /**原本的节点树 */
                const preSubTree = instance.subTree;
                /**拿到更新后的节点树 */
                const subTree = instance.render.call(proxy); //步骤含义和上面初始化时相同
                instance.subTree = subTree; //在这里更新一下节点树
                patch(preSubTree, subTree, container, instance, anchor);
            }
        }, {
            scheduler: () => {
                queueJob(instance.update);
            }
        });
    }
    /**更新组件实例instance，从nextVnode上取出数据更新
     * @param instance 组件实例
     * @param nextVNode 更新之后的虚拟节点
     */
    function updateComponentPreRender(instance, nextVNode) {
        nextVNode.component = instance;
        // const prevProps = instance.vnode.props;// 所以之前的 props 就是基于 instance.vnode.props 来获取 
        instance.vnode = nextVNode; //更新当前vnode
        instance.next = null; //清空next 
        const { props } = nextVNode;
        instance.props = props || {};
    }
    /**处理element类型，分为初始化和更新俩种
     * @param n1 旧vNode （如果是初始化，这个就是null）
     * @param n2 新Vnode
     * @param container 容器
     * @param parentComponent 父组件实例
     * @param anchor 插入的锚点， 如果是需要插入的话，将要插在这个节点之前
     */
    function processElement(n1, n2, container, parentComponent, anchor) {
        if (!n1) {
            mountElement(n2, container, parentComponent, anchor);
        }
        else {
            patchElement(n1, n2, container, parentComponent, anchor);
        }
    }
    /**初始化element
     * @param vnode vnode
     * @param container 容器
     * @param parentComponent 父组件实例
     * @param anchor 插入的锚点， 如果是需要插入的话，将要插在这个节点之前
     */
    function mountElement(vnode, container, parentComponent, anchor) {
        if (typeof vnode.type !== 'string')
            return;
        /**创建的DOM元素 */
        const el = (vnode.el = hostCreateElement(vnode.type)); //创建元素，比如创建 div 、 h1 等，同时给当前的vnode.el赋值
        const { children, props, shapeFlag } = vnode;
        //赋值元素内部内容。children类型可能是vnode[] 或者 string   
        if (shapeFlag & 4 /* ShapeFlags.TEXT_CHILDREN */) { //是string的话
            el.textContent = children; // 这里 children 就是 text ，只需要用DOM操作渲染一下就完事了
        }
        else if (shapeFlag & 8 /* ShapeFlags.ARRAY_CHILDREN */) { //如果是虚拟节点数组，那么就要调用patch
            // 举个栗子
            // render(){ 
            //     return h("div",{},[h("p"),h(Hello)])  //Hello 是个 component
            // }
            // 这里 children 就是个数组了，就需要依次调用 patch 递归来处理
            mountChildren(vnode.children, el, parentComponent, anchor);
        }
        //赋值元素属性
        for (const key in props) {
            if (Object.prototype.hasOwnProperty.call(props, key)) {
                const value = props[key];
                hostPatchProp(el, key, null, value); // 为了实现用户自定义渲染接口
            }
        }
        hostInsert(el, container, anchor); //原本是 container.append(el)，但是为了实现用户自定义渲染接口，所以变成这个
    }
    /**更新Element节点
     * @param n1 旧vNode
     * @param n2 新Vnode
     * @param container 容器
     * @param parentComponent 父组件
     * @param anchor 插入的锚点， 如果是需要插入的话，将要插在这个节点之前
     */
    function patchElement(n1, n2, container, parentComponent, anchor) {
        const oldProps = n1.props || {};
        const newProps = n2.props || {};
        /**该节点的DOM */
        const el = (n2.el = n1.el); //获取el的同时，给n2的el也赋值，这样才能让下一次更新时也能拿到el
        patchProps(el, oldProps, newProps);
        patchChildren(n1, n2, el, parentComponent, anchor);
    }
    /**更新element的props
     * @param el 该element的el，用于挂载属性等
     * @param oldProps 旧props
     * @param newProps 新props
     */
    function patchProps(el, oldProps, newProps) {
        if (oldProps === newProps)
            return; //完全相同就别动，免得浪费性能 （但是这个对比好像没啥用啊？）
        //更新分为下面几种情况
        // 1. 原属性值从 foo 变成 foo-new ，就是普通的更新
        // 2. foo -> undefined 或 null，变成了这种无意义的值，就应该删除
        // 3. newProps对象中，少了oldProps对象中的某个属性，那也是删除
        // 4. newProps对象中比oldProps对象多了某个属性，就是新增
        // oldProps 有，newProps 也有，但是 val 值变更了
        // 举个栗子，之前: oldProps.id = 1 ，更新后：newProps.id = 2 
        // key 存在 oldProps 里，也存在 newProps 内，所以，以newProps为基准 
        for (const key in newProps) { //先遍历 newProps ，在这里判断删除/修改
            /**根据新props的键值，拿到oldProps中对应的旧props  （可能为空，就是被删除了） */
            const preProp = oldProps[key];
            /**新属性 */
            const nextProp = newProps[key];
            if (preProp !== nextProp) { //如果新旧不相同，那就是更新或删除
                hostPatchProp(el, key, preProp, nextProp);
            }
        }
        // oldProps 有，而 newProps 没有了
        // 比如，之前： {id:1,tId:2}  更新后： {id:1}
        // 这种情况下我们就应该以 oldProps 作为基准，因为在 newProps 里面是没有的 tId 的
        // 还需要注意一点，如果这个 key 在 newProps 里面已经存在了，说明已经处理过了，就不要在处理了 
        for (const key in oldProps) { //然后遍历老props，把新的没有的删掉
            const prevProp = oldProps[key];
            const nextProp = null; // 这里是以oldProps为基准来遍历，而且得到的值是newProps内没有的，所以交给host更新的时候，把新的值设置为null
            if (!(key in newProps)) { // 还需要注意一点，如果这个 key 在 newProps 里面已经存在了，说明已经处理过了，就不要在处理了 
                hostPatchProp(el, key, prevProp, nextProp);
            }
        }
    }
    /**对比children
     * @param n1 旧vNode
     * @param n2 新Vnode
     * @param container 父亲容器，用于删除/设置children的DOM节点
     * @param parentComponent 父组件实例
     * @param anchor 插入的锚点， 如果是需要插入的话，将要插在这个节点之前
     */
    function patchChildren(n1, n2, container, parentComponent, anchor) {
        //对比children，有四种情况 （children有两种类型，text的字符串类型或者vnode[]数组类型，所以搭配出四种变化）
        // 1. vnode[] -> string
        // 2. string  -> string
        // 3. string  -> vnode[]
        // 4. vnode[] -> vnode[]
        /**老节点的shapeFlag */
        const preShapeFlag = n1.shapeFlag;
        /**老节点的children */
        const c1 = n1.children;
        /**新节点的shapeFlag */
        const shapeFlag = n2.shapeFlag;
        /**新节点的children */
        const c2 = n2.children;
        if (shapeFlag & 4 /* ShapeFlags.TEXT_CHILDREN */) { //如果新节点是text节点
            //下面这一块的代码可以精简，因为 if和else中，调用hostSetElementText 是一模一样的，但是为了可读性，我就不重构了
            if (preShapeFlag & 8 /* ShapeFlags.ARRAY_CHILDREN */) { //老节点是数组， 说明是情况 1. vnode[] -> string
                //把老的children全部移除
                unmountChildren(n1.children); //这时候children类型一定是 vnode[]
                //然后设置为新的text
                hostSetElementText(container, c2);
            }
            else { //否则老节点就是string，说明是情况 2. string  -> string 
                if (c2 !== c1) { //不相同时才设置
                    hostSetElementText(container, c2);
                }
            }
        }
        else { //如果新节点是数组类型
            if (preShapeFlag & 4 /* ShapeFlags.TEXT_CHILDREN */) { //如果老节点是string， 说明是情况 3. string  -> vnode[]
                hostSetElementText(container, ""); //把老节点的文本设为空
                mountChildren(c2, container, parentComponent, anchor); //然后挂载新的children
            }
            else { //如果老节点是数组，说明是情况 4. vnode[] -> vnode[]，就需要使用diff算法了 
                patchKeyedChildren(c1, c2, container, parentComponent, anchor);
            }
        }
    }
    /**取消挂载Children。实际上就是获取children[i].el，然后移除DOM
     * @param children 虚拟节点的children，是个数组
     */
    function unmountChildren(children) {
        for (let i = 0; i < children.length; i++) {
            /**拿到children的el （DOM元素） */
            const { el } = children[i];
            if (el)
                hostRemove(el);
        }
    }
    /**挂载children，在这里遍历vnode.children数组，每个调用一次patch 。这时是确定children是个数组了的
     * @param children 要被挂载的children
     * @param container 挂载的容器DOM元素
     * @param parentComponent 父组件实例
     * @param anchor 插入的锚点， 如果是需要插入的话，将要插在这个节点之前
     */
    function mountChildren(children, container, parentComponent, anchor) {
        if (!children || typeof children === 'string')
            return;
        children.forEach((v) => {
            patch(null, v, container, parentComponent, anchor);
        });
    }
    /**对比两个 children 数组  。diff算法就在这里
     * @param c1 旧children数组
     * @param c2 新children数组
     * @param container 容器DOM元素
     * @param parentComponent 父组件实例
     * @param parentAnchor 父插入的锚点， 如果是需要插入的话，将要插在这个节点之前
     */
    function patchKeyedChildren(c1, c2, container, parentComponent, parentAnchor) {
        /**左边界索引 */
        let i = 0;
        /**新children数组的长度 */
        const l2 = c2.length;
        /**旧children数组的右边界索引 */
        let e1 = c1.length - 1;
        /**新children数组的右边界索引 */
        let e2 = l2 - 1;
        /**判断两个VNode是否相同 (基于 type 和 key 判断是否相等) */
        const isSameVNodeType = (n1, n2) => {
            return n1.type === n2.type && n1.key === n2.key;
        };
        // 先从左到右，找到左边界。 判断 n1 和 n2 是否相等，遇到不相等就跳出。当前的相等，就进行patch递归
        while (i <= e1 && i <= e2) {
            /**当前索引对应的旧孩子 */
            const n1 = c1[i];
            /**当前索引对应的新孩子 */
            const n2 = c2[i];
            if (!isSameVNodeType(n1, n2)) { //如果不相同
                console.log("两个 child 不相等(从左往右比对)，左边界为", i, `\n旧孩子: `, n1, `\n新孩子: `, n2);
                break;
            }
            patch(n1, n2, container, parentComponent, parentAnchor); //进行patch递归
            i++;
        }
        //此时的i不再开始变化，而是 e1 和 e2 从右往左进行对比 （从后往前判断，看看是否屁股后也有相同的元素，免得插入了中间一个，后面的没变却要重新渲染）
        //遇到不相等就跳出。当前的相等，就把这两个child节点进行patch递归
        while (i <= e1 && i <= e2) {
            /**当前索引对应的旧孩子 */
            const n1 = c1[e1];
            /**当前索引对应的新孩子 */
            const n2 = c2[e2];
            if (!isSameVNodeType(n1, n2)) { //如果不相同
                console.log(`两个 child 不相等(从右往左比对)，右边界分别为 旧 ${e1} 新 ${e2}`, `\n旧孩子: `, n1, `\n新孩子: `, n2);
                break;
            }
            patch(n1, n2, container, parentComponent, parentAnchor); //进行patch递归
            e1--;
            e2--;
        }
        console.log('边界确定', `i = ${i}, e1 = ${e1}`, `e2 = ${e2}`);
        if (i > e1 && i <= e2) {
            // 如果是这种情况的话就说明, 新节点的数量大于旧节点的数量 ==> 也就是说新增了 vnode   (画图可以就理解为什么i在这俩中间)
            // 锚点的计算：新的节点有可能需要添加到尾部，也可能添加到头部，所以需要指定添加的问题
            /**判断要插入锚点的位置 */
            const nextPos = e2 + 1; //应该使用e2 + 1  （而不是 i + 1，因为对于往左侧添加的话，当插入的数量≥2的时候，应该获取到 c2 的第一个元素，i+1就不准确了，可以画图看）
            /**获得要插入的锚点 */
            const anchor = nextPos < l2 ? c2[nextPos].el : parentAnchor; // 如果比新children长度短，就正常以这个为锚点。 否则就越界了，应该使用父亲的Anchor
            while (i <= e2) {
                console.log(`需要新创建一个 vnode:`, c2[i]);
                patch(null, c2[i], container, parentComponent, anchor); //第一个参数为null，代表是创建新节点
                i++; // 光标移动，支持一次新建多个节点 
            }
        }
        else if (i <= e1 && i > e2) {
            // 这种情况的话说明新节点的数量是小于旧节点的数量的，那么我们就需要把多余的删除  
            while (i <= e1) {
                console.log(`需要删除当前的 vnode:  `, c1[i].el);
                hostRemove(c1[i].el);
                i++;
            }
        }
        else {
            //剩下的就是中间对比了， 可以是改顺序了、删除了、新增了等
            //最普通的想法就是遍历中间所有节点，然后一个个去改，但是时间复杂度就是O(n)了，所以可以采用 key 来进行
            //同样也分为多种情况
            // 情况1，总节点数目相同，需要删除老的。a,b,(c,d),f,g --> a,b,(e,c),f,g  
            // 情况2，新节点数目比旧节点少，已经对比完新节点，却还存在老节点没对比，可以直接删除老的 a,b,(e,c,d),f,g --> a,b,(e,c),f,g 
            // 情况3，顺序变化。 a,b,(c,d,e),f,g --> a,b,(e,c,d),f,g 
            // 情况4，节点新增。 a,b,(c,e),f,g --> a,b,(e,c,d),f,g
            /**旧children数组的 中间区间 的左边界 */
            let s1 = i;
            /**新children数组的 中间区间 的左边界 */
            let s2 = i;
            /**需要处理新节点的数量 */
            const toBePatched = e2 - s2 + 1; // 这个可以用于优化情况2
            /**老节点已经被patch处理的数量 */
            let patched = 0; // 这个可以用于优化情况2
            // 怎么判断当前这中间部分，是否有节点需要移动呢 -> 如果发现 所有节点的旧index都是升序的话，说明不需要移动
            //  比如 a,b,(c,d,e),f,g --> a,b,(e,c,d),f,g  ，其中cde的索引从 0 1 2 变成了 1 2 0，不是递增的，所以需要移动
            /**是否有节点需要移动 - 标识符。为true的时候会使用最长递增子序列来进行移动 */
            let moved = false;
            /**当前新索引中，升序部分最大的值，用于判断索引是否为升序 */
            let maxNewIndexSoFar = 0;
            /**根据key查找index的映射Map，键是key，值是index */
            const keyToNewIndexMap = new Map();
            /**- 新索引与旧索引的映射关系。
             * - 初始化为0, 后面处理的时候如果发现是0的话，那么就说明新值在老的里面不存在
             * - 在“遍历旧孩子们”的时候进行修改
             */
            const newIndexToOldIndexMap = new Array(toBePatched).fill(0); //
            //遍历新孩子们，设置 key 与 index 的映射
            for (let j = s2; j <= e2; j++) {
                /**当前索引对应的新孩子 */
                const nextChild = c2[j];
                if (nextChild.key)
                    keyToNewIndexMap.set(nextChild.key, j); //用户有传入key才存
            }
            //遍历旧孩子们
            for (let j = s1; j <= e1; j++) {
                /**当前索引对应的旧孩子 */
                const preChild = c1[j];
                if (patched >= toBePatched) { //优化情况2
                    hostRemove(preChild.el);
                    continue; //下面的就不需要执行了，优化算法速度
                }
                /**这个旧节点的新索引 */
                let newIndex;
                if (preChild.key !== null || preChild.key !== undefined) { //用户有传入key才获取 
                    newIndex = keyToNewIndexMap.get(preChild.key); //从map中取出对应的索引  
                }
                else { // 用户有可能没有传入key，就用索引代替
                    for (let k = s2; k <= e2; k++) { //这时候就需要遍历了。 注意k <= e2，不要忘记等于号了
                        if (isSameVNodeType(preChild, c2[k])) { //判断 preChild 和 新孩子[k] 是否相等
                            newIndex = j;
                            break;
                        }
                    }
                }
                if (newIndex === undefined) { //如果经过上面的查找key，还没找到对应的索引，说明这个旧节点在新孩子数组中不存在，应该删除
                    hostRemove(preChild.el); //移除掉这个DOM
                }
                else { //存在的话就patch，进行更深层次的更新
                    //根据newIndex是否升序，判断是否有节点需要移动
                    if (newIndex >= maxNewIndexSoFar) { //如果一直是升序的
                        maxNewIndexSoFar = newIndex;
                    }
                    else { //一旦发现有的不是升序，就认定有节点需要移动，启动最长递增子序列算法
                        moved = true;
                    }
                    //在这里给 newIndexToOldIndexMap 赋值。 这时候的j就是旧index
                    // newIndex - s2 的含义：因为我们的数组索引是从“变化区域的左侧”开始，而newIndex的索引是从“整个children的左侧”开始，所以需要相减，才是我们真正想赋值的
                    // j + 1 的含义：因为j是从0开始，而在这个数组中，0是有特殊含义的(代表新值在老的里不存在)
                    newIndexToOldIndexMap[newIndex - s2] = j + 1;
                    patch(preChild, c2[newIndex], container, parentComponent, null); //进行patch更新节点
                    patched++; //被处理过的值++
                }
            }
            //使用最长递增子序列优化：因为如果有部分元素是升序的话，那么这些元素就是不需要移动的，只需要移动乱序的即可。
            /** 若有节点需要移动，得到最长递增子序列(下标数组)，仅对移动过的节点处理 */
            const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : []; // 通过 moved 来进行优化，如果没有移动过的话 那么就不需要执行算法（这个算法也是有些时间复杂度的）
            /**最长递增子序列的指针，从 (increasingNewIndexSequence.length - 1) 开始逆序遍历 */
            let point = increasingNewIndexSequence.length - 1;
            //处理 顺序移动或新增 的情况。   使用逆序遍历，才能保证插入时的锚点已经被固定!!!
            for (let j = toBePatched - 1; j >= 0; j--) {
                // 假设toBePatched是5 ， 此时的递增子序列 increasingNewIndexSequence 是[1, 2, 4] ， point和j从末尾开始逆序
                // j=4时，point=2，递增子序列[point] = 4， j === 4，则point--
                // j=3时，ponit=1，递增子序列[point] = 2， j !== 2，则移动位置
                // j=2时，ponit=1，递增子序列[point] = 2， j === 2，则point--
                // j=1时，point=0，递增子序列[point] = 1， j === 1，则point--
                /**确定当前要处理的节点索引 */
                const nextIndex = s2 + j; // 因为j是 “中间区间” 的索引，而我们要拿到的孩子应该是在整个children数组中的索引
                /**根据当前要处理的索引，拿到新孩子数组中指定的孩子 */
                const nextChild = c2[nextIndex];
                /**要插入的锚点。锚点等于当前节点索引+1，也就是当前节点的后面一个节点。 因为是倒遍历，所以锚点是位置确定的节点，不会变动了 */
                const anchor = nextIndex + 1 < l2 ? c2[nextIndex + 1].el : parentAnchor; //  判断是否越界，越界了就用默认的
                if (newIndexToOldIndexMap[j] === 0) { //如果当前值是0，说明要创建
                    patch(null, nextChild, container, parentComponent, anchor); //进行patch创建新节点
                }
                else if (moved) { //需要移动的时候才
                    if (j !== increasingNewIndexSequence[point] || point < 0) { //除了判断j是否不相等， 还有point小于0的时候，就直接移动就行
                        //如果当前j和递增子序列的值不相同，说明是“非递增子序列”，需要移动位置
                        console.log(nextChild.el, '这个节点要移动位置');
                        hostInsert(nextChild.el, container, anchor); //在指定锚点的前面插入DOM，即 container.insertBefore(el, anchor)
                    }
                    else {
                        point--; //不需要移动的话，说明匹配成功递增子序列了，就 point-- 即可
                    }
                }
            }
        }
    }
    /**处理Fragment标签 （只渲染孩子）
     * @param n1 旧虚拟节点 （如果是初始化，这个就是null）
     * @param n2 新虚拟节点
     * @param container 容器DOM
     * @param parentComponent 父组件实例
     * @param parentAnchor 父组件的插入的锚点， 如果是需要插入的话，将要插在这个节点之前
     */
    function processFragment(n1, n2, container, parentComponent, anchor) {
        if (n2.children)
            mountChildren(n2.children, container, parentComponent, anchor); //只渲染孩子
    }
    /**处理Text文本节点 （只渲染文字，不渲染标签）
     * @param n1 旧虚拟节点 （如果是初始化，这个就是null）
     * @param n2 新虚拟节点
     * @param container 容器
     */
    function processText(n1, n2, container) {
        const { children } = n2; //这里的 children 就是用户传来的字符串Text
        const textNode = (n2.el = document.createTextNode(children)); //记得顺便给vnode.el赋值 （在mountElement的时候也做了这事，由于无法复用，所以这里也要这么做）
        container.append(textNode);
    }
    //#endregion
    return {
        createApp: createAppAPI(render)
    };
}
/**获得最长递增子序列的索引数组
 * @param arr 要被查找的数组
 * @returns 返回最长子序列的下标数组。 比如 传入[4,2,3,1,5] ，得到 [1,2,4]  （即最长递增序列是 2 3 5）
 */
function getSequence(arr) {
    const p = arr.slice();
    const result = [0];
    let i, j, u, v, c;
    const len = arr.length;
    for (i = 0; i < len; i++) {
        const arrI = arr[i];
        if (arrI !== 0) {
            j = result[result.length - 1];
            if (arr[j] < arrI) {
                p[i] = j;
                result.push(i);
                continue;
            }
            u = 0;
            v = result.length - 1;
            while (u < v) {
                c = (u + v) >> 1;
                if (arr[result[c]] < arrI) {
                    u = c + 1;
                }
                else {
                    v = c;
                }
            }
            if (arrI < arr[result[u]]) {
                if (u > 0) {
                    p[i] = result[u - 1];
                }
                result[u] = i;
            }
        }
    }
    u = result.length;
    v = result[u - 1];
    while (u-- > 0) {
        result[u] = v;
        v = p[v];
    }
    return result;
}

// 这个文件夹是 createRenderer 函数的DOM实现  （用户可以自行传入自定义实现，这里是默认的用于DOM的）
/**创建元素   详细类型注释查看ts类型 */
const createElement = (vnodeType) => {
    return document.createElement(vnodeType);
};
/**给DOM结点挂载属性  详细类型注释查看ts类型*/
const patchProp = (el, key, preVal, nextVal) => {
    /**判断是否是以on开头，且首字母大写的 */
    const isOn = (key) => /^on[A-Z]/.test(key);
    if (isOn(key)) { //如果是事件的话
        const event = key.slice(2).toLowerCase(); //去掉on，转小写，就是 addEventListener 的事件名了
        el.addEventListener(event, nextVal);
    }
    else { //如果是属性的话
        if (nextVal === undefined || nextVal === null) { //如果新prop为空，就删掉这个属性
            el.removeAttribute(key);
        }
        else { //不然就是设置  （可以是新增，也可以是修改）
            el.setAttribute(key, nextVal);
        }
    }
};
/**在指定结点前插入DOM元素  详细类型注释查看ts类型*/
const insert = (child, parent, anchor) => {
    parent.insertBefore(child, anchor || null); // 调用DOM API 插入到指定元素前面，第一个参数是要被插入的元素，第二个参数是“在谁前面”
};
/**移除DOM元素 移除单个孩子   详细类型注释查看ts类型*/
const remove = (child) => {
    /**拿到这个孩子的父节点 */
    const parent = child === null || child === void 0 ? void 0 : child.parentNode;
    if (parent) { //在父级身上把它删了
        parent.removeChild(child);
    }
};
/**将元素的孩子设置为text   详细类型注释查看ts类型*/
const setElementText = (el, text) => {
    el.textContent = text;
};
/**默认的DOM渲染器 （createRenderer函数的DOM实现） */
const renderer = createRenderer({
    createElement,
    patchProp,
    insert,
    remove,
    setElementText
});
/**创建APP组件    真正的 createApp 函数在 src\runtime-core\createApp.ts 中，这里使用高阶函数产生了一个默认用DOM渲染器的createAPP
 * @param  rootComponent  根组件
 */
function createApp(rootComponent) {
    return renderer.createApp(rootComponent);
}

exports.createApp = createApp;
exports.createRenderer = createRenderer;
exports.createTextVNode = createTextVNode;
exports.getCurrentInstance = getCurrentInstance;
exports.h = h;
exports.inject = inject;
exports.provide = provide;
exports.proxyRefs = proxyRefs;
exports.reactive = reactive;
exports.ref = ref;
exports.renderSlots = renderSlots;
